package me.zhengjie.tool;

import com.google.common.base.Preconditions;

import java.util.Calendar;

/**
 * 分布式全局ID生成器
 * 1、雪花算法
 * 2、与IP相关
 * 3、长度18位
 * 使用示例
 *         System.out.println(DateUtil.getCurDateTime());
 *         int j = 0;
 *         String lastId = "";
 *         IdGenerator generator = IdGenerator.instance; //初始化
 *         for (int i = 0; i< 8000000; i++) {
 *             j = j + 1;
 *             lastId = generator.nextStringId(); //获取下一序列
 *         }
 *         System.out.println(DateUtil.getCurDateTime());
 *         System.out.println(lastId);
 *         System.out.println(j);
 *
 * 4、生成的id可以用AnalyticalSnowflakeId类进行反向解析（可参考AnalyticalSnowflakeId的main方法）
 */
public final class IdGenerator {

    public static final long EPOCH;

    private static final long SEQUENCE_BITS = 12L;

    private static final long WORKER_ID_BITS = 10L;

    private static final long SEQUENCE_MASK = (long)((1 << SEQUENCE_BITS) - 1);

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;

    private static long workerId;

    public static IdGenerator instance = new IdGenerator();

    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    private IdGenerator() {
        long workid = getWorkId();
        Preconditions.checkArgument(workid >= 0L && workid < WORKER_ID_MAX_VALUE);
        IdGenerator.workerId = getWorkId();
    }

    private long sequence;

    private long lastTime;

    private static void setWorkerId(final long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        IdGenerator.workerId = workerId;
    }

    /**
     * 获取下一序列号
     * @return String
     */
    public synchronized String nextStringId() {
        long currentMillis = System.currentTimeMillis();
        Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis);
        if (lastTime == currentMillis) {
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }
        lastTime = currentMillis;
        return String.valueOf(((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence);
    }

    /**
     * 获取下一序列号
     * @return Number
     */
    public synchronized Number nextNumberId() {
        long currentMillis = System.currentTimeMillis();
        Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis);
        if (lastTime == currentMillis) {
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }
        lastTime = currentMillis;
        return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }

    private long waitUntilNextTime(final long lastTime) {
        long time = System.currentTimeMillis();
        while (time <= lastTime) {
            time = System.currentTimeMillis();
        }
        return time;
    }

    /**
     * 获取workid
     * @return
     */
    private static long getWorkId() {
        String sIP = MachineUtil.getIp();
        String[] num = sIP.split("\\.");
        long workid = 0L;
        for (String s : num){
            workid = workid + Long.valueOf(s).longValue();
        }
        return workid;
    }
}
